using System;
using System.Collections.Generic;
using System.Text;
using Barrage.Meshes;
using SlimDX.Direct3D9;
using SlimDX;
using Barrage.Shaders;

namespace Barrage.Items
{
    /// <summary>
    /// Holds data that identifies each particle in a particleemitter
    /// Since the particles' behavior are deterministic, it is enough to hold their birthTime and their random seed to get their current data
    /// </summary>
    public struct ParticleData
    {
        /// <summary>
        /// Time at which the particle started. Since their behavior is deterministic, this is enough to get their current position
        /// </summary>
        public float birthTime;
        /// <summary>
        /// Random seed user to determine the particle's random data
        /// </summary>
        public int randomSeed;        
    }
    /// <summary>
    /// Emits a number of particles with a specified base data (speed, color, random variations...).
    /// Uses a ParticleMesh for rendering the particle data.
    /// </summary>
    public class ParticleEmitter: Item
    {
        Random seedGenerator;

        ParticleMesh particles;

        Vector3 baseSpeed = new Vector3(0,0,-1);
        float baseSize = 100;
        Vector3 baseColor = new Vector3(0.2f, 0.2f, 0.8f);
        
        Vector3 randomSpeed = new Vector3(0.1f, 0.1f, 0.1f);
        Vector3 randomOrigin = new Vector3(0.1f,0.1f,0.1f);
        Vector3 randomColor = new Vector3(0.2f,0.2f,0.2f);
        float randomSize = 50;
        
        Vector3 colorVariation = new Vector3(-0.05f, -0.05f, -0.05f);
        float sizeVariation = -50;
        Vector3 gravity = new Vector3(0,-0.1f,0);
        
        float lifespan=2.5f;
        /// <summary>
        /// Particle birth per second
        /// </summary>
        float birthRate = 50;

        // To ensure particle birth even at high framerates, we store the remainder between frames
        float birthOffset = 0;

        List<ParticleData> particleData = new List<ParticleData>(100);
   
        /// <summary>
        /// Constructor. Inits the mesh data
        /// </summary>
        /// <param name="tex">Texture that will be used to render the particles</param>
        public ParticleEmitter(Texture tex)
        {
            seedGenerator = new Random();
            particles = new ParticleMesh(tex,new D3DShader("ShipParticle.fx"));
            addMesh(particles);
        }

        /// <summary>
        /// \internal Updates particle data
        /// </summary>
        protected override void DoUpdate()
        {
            // Check for births / deaths
            // Deaths
            float currentTime = this.Timer.TotalTimeSeconds;
            // Use a stack so the last deaths are the first removed (avoid reordering)
            Stack<int> deaths = new Stack<int>();
            for (int i = 0; i < particleData.Count; i++)
            {
                float birth = particleData[i].birthTime;
                if ((currentTime - birth) > lifespan)
                    deaths.Push(i);
            }
            while(deaths.Count>0)
                particleData.RemoveAt(deaths.Pop());
            // Births
            float lastInterval = this.Timer.LastIntervalSeconds;
            birthOffset += birthRate * lastInterval;
            int births = (int)Math.Truncate(birthOffset);
            births = Math.Min((ParticleMesh.MAX_PARTICLES-particleData.Count),births);
            for (int i = births-1; i >= 0 ; i--)
            {
                // Add a little randomness if more than one birth occured this frame
                ParticleData data = new ParticleData();
                data.birthTime = currentTime - i / birthRate;
                data.randomSeed = seedGenerator.Next();
                particleData.Add(data);
            }


            birthOffset -= births;

            // Update positions
            int totalParticles= Math.Min(particleData.Count, ParticleMesh.MAX_PARTICLES);
            for (int i = 0; i < totalParticles; i++)
            {
                ParticleData data = particleData[i];
                float lifetime = currentTime-data.birthTime;
                Random r=new Random(data.randomSeed);
                Vector3 initPosition = new Vector3(((float)r.NextDouble() * 2 - 1) * this.randomOrigin.X, ((float)r.NextDouble() * 2 - 1) * this.randomOrigin.Y, ((float)r.NextDouble() * 2 - 1) * this.randomOrigin.Z);
                Vector3 originalSpeed = this.baseSpeed + new Vector3(((float)r.NextDouble() * 2 - 1) * this.randomSpeed.X, ((float)r.NextDouble() * 2 - 1) * this.randomSpeed.Y, ((float)r.NextDouble() * 2 - 1) * this.randomSpeed.Z);
                Vector3 finalPosition = initPosition + originalSpeed * lifetime + this.gravity * lifetime * lifetime;
                float initSize = baseSize + ((float)r.NextDouble() * 2 - 1) * randomSize;
                float finalSize = initSize + this.sizeVariation * lifetime;

                Vector3 baseColor = this.baseColor + new Vector3(((float)r.NextDouble() * 2 - 1) * this.randomColor.X,((float)r.NextDouble() * 2 - 1) * this.randomColor.Y,((float)r.NextDouble() * 2 - 1) * this.randomColor.Z);
                Vector3 finalColor = baseColor + this.colorVariation * lifetime;
                particles.ParticleData[i].Position = finalPosition;
                particles.ParticleData[i].Color = new Color4(1,Math.Max(0,finalColor.X),Math.Max(0,finalColor.Y),Math.Max(0,finalColor.Z));
                particles.ParticleData[i].Size = Math.Max(0,finalSize);
            }
            particles.ParticleNumber = totalParticles;
            
            particles.UpdateData();


            base.DoUpdate();
        }

        /// <summary>
        /// \internal Clear resources
        /// </summary>
        public override void Dispose()
        {
            particles = null;
            base.Dispose();
        }
    }
}
